﻿using System;

namespace Zive.Color {

    public static class RgbColorConverter {

        //
        // Forward conversion: RGB to XYZ
        //

        public static XyzColor Convert( RgbColor inputColor ) {
            // rescale to 0..1
            double r = inputColor.R / 255d;
            double g = inputColor.G / 255d;
            double b = inputColor.B / 255d;

            // inverse compand
            r = InverseCompand( r );
            g = InverseCompand( g );
            b = InverseCompand( b );

            // transform
            var transform = RgbConversionMatricesFactory.Create( inputColor.Illuminant ).RgbToXyz;
            double x = r * transform[ 0, 0 ] + g * transform[ 0, 1 ] + b * transform[ 0, 2 ];
            double y = r * transform[ 1, 0 ] + g * transform[ 1, 1 ] + b * transform[ 1, 2 ];
            double z = r * transform[ 2, 0 ] + g * transform[ 2, 1 ] + b * transform[ 2, 2 ];

            return new XyzColor( inputColor.Illuminant, x, y, z );
        }

        public static XyzColor Convert( RgbColor inputColor, Illuminants newIlluminant ) {
            var tempColor = Convert( inputColor );
            if ( tempColor.Illuminant != newIlluminant ) {
                tempColor = BradfordTransformFactory.Create( tempColor.Illuminant, newIlluminant ).Transform( tempColor );
            }
            return tempColor;
        }

        //
        // Reverse conversion: XYZ to RGB
        //

        public static RgbColor Convert( XyzColor inputColor ) {
            double x = inputColor.X;
            double y = inputColor.Y;
            double z = inputColor.Z;

            // transform
            double[,] transform = RgbConversionMatricesFactory.Create( inputColor.Illuminant ).XyzToRgb;
            double r = x * transform[ 0, 0 ] + y * transform[ 0, 1 ] + z * transform[ 0, 2 ];
            double g = x * transform[ 1, 0 ] + y * transform[ 1, 1 ] + z * transform[ 1, 2 ];
            double b = x * transform[ 2, 0 ] + y * transform[ 2, 1 ] + z * transform[ 2, 2 ];

            // compand
            r = Compand( r );
            g = Compand( g );
            b = Compand( b );

            // and rescale
            double R = Clip( r * 255d );
            double G = Clip( g * 255d );
            double B = Clip( b * 255d );
            return new RgbColor( inputColor.Illuminant, R, G, B );
        }

        public static RgbColor Convert( XyzColor inputColor, Illuminants newIlluminant ) {
            var tempColor = inputColor;
            if ( newIlluminant != tempColor.Illuminant ) {
                tempColor = BradfordTransformFactory.Create( tempColor.Illuminant, newIlluminant ).Transform( tempColor );
            }
            return Convert( tempColor );
        }

        //
        // Implementation
        //

        private static double Clip( double value ) {
            if ( value < 0d )
                return 0d;
            if ( value > 255d )
                return 255d;
            return value;
        }

        private static double Compand( double q ) {
            if ( q > 0.0031308d ) {
                return 1.055d * Math.Pow( q, 1d / 2.4d ) - 0.055d;
            }
            return 12.92 * q;
        }

        private static double InverseCompand( double q ) {
            if ( q > 0.04045d ) {
                return Math.Pow( ( q + 0.055d ) / 1.055d, 2.4d );
            }
            return q / 12.92d;
        }

    }

}
